# Ковариантность типов

Вариантность параметра типа управляет подтипом параметризованных типов (таких, как классы или trait-ы).

Чтобы разобраться в вариантности, рассмотрим следующий пример типов:

```dotty
trait Item:
  def productNumber: String

trait Buyable extends Item:
  def price: Int

trait Book extends Buyable:
  def isbn: String
```

Предположим также следующие параметризованные типы:

```dotty
// пример инвариантного типа
trait Pipeline[T]:
  def process(t: T): T

// пример ковариантного типа
trait Producer[+T]:
  def make: T

// пример контрвариантного типа
trait Consumer[-T]:
  def take(t: T): Unit
```

В целом существует три режима вариантности (**variance**):

- инвариант (**invariant**) — значение по умолчанию, написанное как `Pipeline[T]`
- ковариантный (**covariant**) — помечен знаком `+`, например `Producer[+T]`
- контравариантный (**contravariant**) — помечен знаком `-`, как в `Consumer[-T]`

Подробнее рассмотрим, что означает и как используется эта аннотация.

### Инвариантные типы

По умолчанию такие типы, как `Pipeline`, инвариантны в своем аргументе типа (в данном случае `T`). 
Это означает, что такие типы, как `Pipeline[Item]`, `Pipeline[Buyable]` и `Pipeline[Book]`, 
не являются подтипами друг друга.

Предположим, что следующий метод использует два значения (`b1`, `b2`) типа `Pipeline[Buyable]` 
и передает свой аргумент `b` методу `process` при его вызове на `b1` и `b2`:

```dotty
def oneOf(
  p1: Pipeline[Buyable],
  p2: Pipeline[Buyable],
  b: Buyable
): Buyable =
  val b1 = p1.process(b)
  val b2 = p2.process(b)
  if b1.price < b2.price then b1 else b2
```

Напомним, что отношения подтипов между типами следующие: `Book <: Buyable <: Item`.

Мы не можем передать `Pipeline[Book]` методу `oneOf`, 
потому что в реализации `oneOf` мы вызываем `p1` и `p2` со значением типа `Buyable`. 
`Pipeline[Book]` ожидает `Book`, что потенциально может вызвать _runtime error_.

Мы не можем передать `Pipeline[Item]`, потому что вызов `process` обещает вернуть `Item`; 
однако мы должны вернуть `Buyable`.

##### Почему Инвариант?

На самом деле тип `Pipeline` должен быть инвариантным, 
так как он использует свой параметр типа `T` _и в качестве аргумента, и в качестве типа возвращаемого значения_. 
По той же причине некоторые типы в библиотеке коллекций Scala, такие как `Array` или `Set`, 
также являются инвариантными.

### Ковариантные типы

В отличие от `Pipeline`, который является инвариантным, 
тип `Producer` помечается как ковариантный (**covariant**) путем добавления к параметру типа префикса `+`. 
Это допустимо, так как параметр типа используется только _в качестве типа возвращаемого значения_.

Пометка типа как ковариантного означает, что мы можем передать (или вернуть) `Producer[Book]` там, 
где ожидается `Producer[Buyable]`. И на самом деле, это разумно. 
Тип `Producer[Buyable].make` только обещает вернуть `Buyable`. 
Но для пользователей `make`, так же допустимо принять `Book`, который является подтипом `Buyable`.

Это иллюстрируется следующим примером, где функция `makeTwo` ожидает `Producer[Buyable]`:

```dotty
def makeTwo(p: Producer[Buyable]): Int =
  p.make.price + p.make.price
```

Допустимо передать в `makeTwo` производителя книг:

```dotty
val bookProducer: Producer[Book] = ???
makeTwo(bookProducer)
```

Вызов `price` в рамках `makeTwo` по-прежнему действителен и для `Book`.

##### Ковариантные типы для неизменяемых контейнеров

Ковариантность чаще всего встречается при работе с неизменяемыми контейнерами, 
такими как `List`, `Seq`, `Vector` и т.д.

Например, `List` и `Vector` определяются приблизительно так:

```dotty
class List[+A] ...
class Vector[+A] ...
```

Таким образом, можно использовать `List[Book]` там, где ожидается `List[Buyable]`. 
Это также интуитивно имеет смысл: 
если ожидается коллекция вещей, которые можно купить, то вполне допустимо получить коллекцию книг. 
В примере выше у книг есть дополнительный метод `isbn`, но дополнительные возможности можно игнорировать.

### Контравариантные типы

В отличие от типа `Producer`, который помечен как ковариантный, 
тип `Consumer` помечен как контравариантный (**contravariant**) путем добавления к параметру типа префикса `-`. 
Это допустимо, так как параметр типа используется только _в позиции аргумента_.

Пометка его как контравариантного означает, что можно передать (или вернуть) `Consumer[Item]` там, 
где ожидается `Consumer[Buyable]`. 
То есть у нас есть отношение подтипа `Consumer[Item] <: Consumer[Buyable]`. 
Помните, что для типа `Producer` все было наоборот, и у нас был `Producer[Buyable] <: Producer[Item]`.

Метод `Consumer[Item].take` принимает `Item`. 
Как вызывающий `take`, мы также можем предоставить `Buyable`, 
который будет с радостью принят `Consumer[Item]`, 
поскольку `Buyable` — это подтип `Item`, то есть, по крайней мере, `Item`.

##### Контравариантные типы для потребителей

Контравариантные типы встречаются гораздо реже, чем ковариантные типы. 
Наиболее важным типом, помеченным как контравариантный, с которым можно столкнуться, является тип функций:

```dotty
trait Function[-A, +B]:
  def apply(a: A): B
```

Тип аргумента `A` помечен как контравариантный `A` — он использует значения типа `A`. 
Тип результата `B`, напротив, помечен как ковариантный — он создает значения типа `B`.

Вот несколько примеров, иллюстрирующих отношения подтипов, вызванные аннотациями вариантности функций:

```dotty
val f: Function[Buyable, Buyable] = b => b

// OK - допустимо вернуть Buyable там, где ожидается Item
val g: Function[Buyable, Item] = f

// OK - допустимо передать аргумент Book туда, где ожидается Buyable
val h: Function[Book, Buyable] = f
```

### Резюме

В этом разделе были рассмотрены три различных вида вариантности:

- **Producers** обычно ковариантны и помечают свой параметр типа со знаком `+`. Это справедливо и для неизменяемых коллекций.
- **Consumers** обычно контравариантны и помечают свой параметр типа со знаком `-`.
- Типы, которые являются **одновременно** производителями и потребителями, 
должны быть инвариантными и не требуют какой-либо маркировки для параметра своего типа. 
В эту категорию, в частности, попадают изменяемые коллекции, такие как `Array`.


---

**Ссылки:**

- [Contravariance in Scala: Why Is It So Hard? - Rock the JVM](https://www.youtube.com/watch?v=b1ftkK1zhxI&list=PLmtsMNDRU0Bzj7INIrLugi3a_WClwQuiS&index=2)
- [Of variance and functors](https://typelevel.org/blog/2016/02/04/variance-and-functors.html)
- [Scala3 book](https://docs.scala-lang.org/scala3/book/types-variance.html)
- [Type Parameterisation](https://danielasfregola.com/2015/02/02/type-parameterisation)
- [Variance, a Scala language concept](https://www.scala-algorithms.com/Variance/)
- [Vivisecting the Veil of Variance](https://www.youtube.com/watch?v=dPbbqrhm-WY)
